#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "options.h"

// se debe completar la extructura mainopts2 con las opciones a pasar.
extern struct option2 mainopts2[];

struct option *mainopts;
int *ini_values;
char **ini_argv;

int ini_lookup(const char * clavem);


/* \brief Devuelve el valor (int) del parámetro de línea de comando indicado en clave. El parámetro debe tener el valor OPT_VAL_IS_INT en el campo mainopts2.opts.val (cuarto parámetro)
 * \param Clave debe coincidir con el parámetro tal y como se aparece en la extructura mainopts
 * \param Next No devuelve el valor del parámetro clave, sino del parámetro situado 
next veces después de clave en la extructura mainopts2
 * \return el valor del parámetro. Basura si el parámetro no es de tipo OPT_VAL_IS_INT
 */

int ini_opt(const char * clave, int next)
{
    int index = ini_lookup(clave);
    return ini_values[index + next];
}


/* \brief Devuelve el valor (cadena de caracteres) del  parámetro de línea de comando indicado en clave. El parámetro debe tener el valor OPT_VAL_IS_CHAR en el campo mainopts2.opts.val (cuarto parámetro)
 * \param Clave debe coincidir con el parámetro tal y como se aparece en la extructura mainopts
 * \param Next No devuelve el valor del parámetro clave, sino del parámetro situado 
next veces después de clave en la extructura mainopts2
 * \return Un puntero a una cadena de caracteres con el argumento pasado. Basura si el parámetro no es de tipo OPT_VAL_IS_CHAR
 */

const char * ini_opts(const char * clave, int next)
{
    int index = ini_lookup(clave);
    return ini_argv[index + next];
}


/* \brief Establece el valor (int) del  parámetro de línea de comando indicado en clave. El parámetro debe tener el valor OPT_VAL_IS_INT en el campo mainopts2.opts.val (cuarto parámetro)
 * \param Clave debe coincidir con el parámetro tal y como se aparece en la extructura mainopts
 * \param val Nuevo valor para el parámetro
 * \param Next No devuelve el valor del parámetro clave, sino del parámetro situado 
next veces después de clave en la extructura mainopts2
 * \return el valor anterior del parámetro. Basura si el parámetro no es de tipo OPT_VAL_IS_INT
 */

int ini_setopt(const char * clave, int val, int next)
{
    int r;
    int index = ini_lookup(clave);
    r = ini_values[index + next];
    ini_values[index+next] = val;
    return r;
}

/* \brief Establece el valor (cadena de caracteres) del  parámetro de línea de comando indicado en clave. El parámetro debe tener el valor OPT_VAL_IS_CHAR en el campo mainopts2.opts.val (cuarto parámetro)
 * \param Clave debe coincidir con el parámetro tal y como se aparece en la extructura mainopts
 * \param Next No devuelve el valor del parámetro clave, sino del parámetro situado 
next veces después de clave en la extructura mainopts2
 * \param val Nueva cadena de caracteres como argumento para el parámetro indicado en clave + next
 * \return Un puntero a una caracteres con el nuevo argumento pasado para el parámetro. Basura si el parámetro no es de tipo OPT_VAL_IS_CHAR
 */

const char * ini_setopts(const char * clave, const char * val, int next)
{
    int index = ini_lookup(clave);
    strncpy(ini_argv[index + next], val, MAX_ARGUMENT_VAL);
    return ini_argv[index+next];
}

/* \brief Inicializa la lectura de los parámetros de línea de comandos
 * \param argc argc del main
 * \param argv argv del main
 * \param ini_file Archivo que contiene más parámetro a pasar al ejecutable. Estos se leen antes que los parámetros escritos en línea de comandos, y por tanto, los últimos sobreescriben a los del archivo. 
 * \return 0 si se indicó llamada a los parámetros --help o -h. 1 en otro caso.
 */
int leer_opciones(int argc, char * argv[], const char * ini_file)
{
    int index;
    int r, i;
    int noptions;
    
    //contar en número de parámetros de la extructura mainopts2
    index = 0;
    while (mainopts2[index].opts.name != NULL) 
	index++;
    noptions = index;

    //arrays que contendrán los valores int ó char de los distintos posibles parámetros
    ini_values = new int[noptions+1];
    ini_argv = new char*[noptions+1];

    //declaramos la estructura options que se pasará a getopt_long_only
    mainopts = new struct option[noptions+1];

    //y se copian los valores de option2 a option
    for (i=0; i<noptions+1; i++)
    {
	mainopts[i] = mainopts2[i].opts;
	if (mainopts2[i].description == NULL)
	    mainopts2[i].description = "";
	ini_values[i] = mainopts2[i].inival;
	ini_argv[i] = new char[MAX_ARGUMENT_VAL];
	if (mainopts2[i].inichar == NULL)
	    strcpy(mainopts2[i].inichar, "");
	strncpy(ini_argv[i], mainopts2[i].inichar, MAX_ARGUMENT_VAL-1);
    }
    //Indica a getopt_long que se muestren los error que aparezcan en getopt_long_only
    opterr = 1;

    //además de las opciones de linea de comandos, 
    // antes se procesarán las opciones del archivo ini_file
    int argc2=0; //nuevo argc ampliado con las opciones del ini_file que se pasará a getopt_long
    char *argv2[200]; //nuevo argv ampliado con ini_file que se pasará a getopt_long

    char buffer[400];
    FILE *f = NULL;
    char *bufaux;

    //lee las opciones del archivo de configuración ini_file
    if (ini_file != NULL)
	f = fopen(ini_file, "r");
    //copia primero el nombre del ejecutable (primer argumento de argv)
    for (i=0; i<1; i++)
    {
	argv2[argc2] = new char[sizeof(buffer)];;
	strcpy(argv2[argc2], argv[i]);
	argc2++;
    }
    //copia las opciones del archivo de configuración ini_file, si existen
    while (f != NULL && NULL != fgets(buffer, sizeof(buffer), f))
    {
	if (buffer[0] == '#' || buffer[0] == ';' || buffer[0] == '/') //comentario
	    continue; //se trata de un comentario. Se salta.
	argv2[argc2] = new char[sizeof(buffer)];
	strcpy(argv2[argc2], buffer);
	//elimina el retorno de carro del final de la linea
	bufaux = &(argv2[argc2][strlen(argv2[argc2])-1]);
	if (*bufaux == '\n' || *bufaux == '\r')
	    *bufaux = '\0'; 
	argc2++;
    }
    //copia las opciones pasadas por linea de comandos, detrás para que se procesen después
    for (i=1; i<argc; i++)
    {
	argv2[argc2] = new char[sizeof(buffer)];;
	strcpy(argv2[argc2], argv[i]);
	argc2++;
    }

    //procesa las opciones
    while((r=getopt_long_only(argc2, argv2, "", mainopts, &index)) >= 0)
    {
	if (r == OPT_AFFECT_OTHER)
	{  
	    //el parámetro es sinónimo de otro, indicado por el offset inival
	    index += mainopts2[index].inival;
	    r = mainopts[index].val;
	}
	else if (r == OPT_NEG_OTHER)
	{
	    // el parámetro niega otro, indicado por el offset inival
	    index += mainopts2[index].inival;
	    r = !mainopts[index].val;
	}
	//procesa el parámetro
	switch (r)
	{
	case true:
	case false:
	    if (mainopts[index].has_arg)
		printf("Error en la configuración de comandos.1 . Arg %s.\n\n", argv2[optind-1]);
	    ini_values[index] = r;
	    break;
	case OPT_AFFECT_OTHER:
	case OPT_NEG_OTHER:
	    printf("Error en la configuración de comandos.2 . Arg %s.\n\n", argv2[optind-1]);
	    break;
	case OPT_VAL_IS_INT:
	    if (!mainopts[index].has_arg)
		printf("Error en la configuración de comandos.3 . Arg %s.\n\n", argv2[optind-1]);
	    else
		//OPT_VAL_IS_INT indica que el argumento es un entero, y se copia al puntero
		ini_values[index]=atoi(optarg);
	    break;
	case OPT_VAL_IS_CHAR:
	    if (!mainopts[index].has_arg)
		printf("Error en la configuración de comandos.4 . Arg %s.\n\n", argv2[optind-1]);
	    else
		//OPT_VAL_IS_CHAR indica que el argumento es una cadena, y se copia al puntero
		strncpy(ini_argv[index], optarg, MAX_ARGUMENT_VAL);
	    break;
	case '?': //error 
	default:
	    printf("Error en las opciones de comando. Arg %s.\n\n", argv2[optind-1]);
	    ini_setopt("h", true);
	    break;
	}
    }

    //borra la estructura option creada y los datos de argv2
    for (i=0; i<argc2; i++)
	delete []argv2[i];

    //si se indicó --options en la linea de comandos, muestra el valor de todos los parámetros
    if (ini_opt("options"))
    {
	printf("\nOpciones actuales:\n");
	for (i=0; i<noptions; i++)
	{
	    switch (mainopts[i].val)
	    {
	    case true:
	    case false:
	    case OPT_VAL_IS_INT:
		printf("%s -> %d\n", mainopts[i].name, ini_opt(mainopts[i].name));
		break;
	    case OPT_VAL_IS_CHAR:
		printf("%s -> %s\n", mainopts[i].name, ini_opts(mainopts[i].name));
		break;
	    }	    
	}
    }
    //si se indicó --help, muestra la ayuda
    if (ini_opt("help"))
    {
	printf("Interfaz_captura:\n"
	       "Captura de video de cámaras Firewire, y grabación al disco.\n\n"
	       "Opciones:\n\n");
	for (i=0; i<noptions; i++)
	{
	    char *nextbuf, *nextbuf2, ch;
	    bool primeralinea=true;
	    if (mainopts[i].has_arg == 1)
		printf("  --%s=OPT\r", mainopts[i].name);
	    else if (mainopts[i].has_arg == 2)
		printf("  --%s [=OPT]\r", mainopts[i].name);
	    else 
		printf("  --%s\r", mainopts[i].name);

	    strcpy(buffer, mainopts2[i].description);
	    nextbuf = buffer;
	    while (strlen(nextbuf) > 50)
	    {
		ch=nextbuf[50];
		nextbuf[50] = '\0';
		nextbuf2 = strrchr(nextbuf, ' ');
		*nextbuf2 = '\0';
		nextbuf[50] = ch;
		printf("\t\t\t%s%s\n", (primeralinea?"":"  "), nextbuf);
		nextbuf = nextbuf2+1;
		primeralinea=false;
	    }
	    printf("\t\t\t%s%s\n", (primeralinea?"":"  "), nextbuf);

	}
	return 0;
    }
    //si se indicó -h, muestra la lista de parámetros. Optición usada por /etc/bash_completions.d/capturar para autocompletar los parámetros en linea de comandos
    else if (ini_opt("h"))
    {
	for (i=0; i<noptions; i++)
	{
	    if (mainopts[i].has_arg >= 1)
		printf("--%s= ", mainopts[i].name);
	    else 
		printf("--%s ", mainopts[i].name);
	}
	printf("\n");
	return 0;
    }

    return 1;
}


/* \brief Borrar todo el espacio de memoria reservado para la lectura y almacenamiento de los parámetros de línea de comandos
 */

void fin_opciones()
{
    int i;
    delete []ini_values;
    for (i=0; mainopts[i].name != NULL; i++)
	delete []ini_argv[i];
    delete []ini_argv[i];
    delete []ini_argv; 
    delete []mainopts;
}


/* \brief Busca el parámetro en la lista de todos los posibles parámetros de entrada
 * \param clave Parámetro a buscar
 * \return El índice del parámetro dentro de la extructura mainopts2
 */
int lastindex;

int ini_lookup(const char * clave)
{
    int index=0;
    if (strcmp(mainopts[lastindex].name, clave) == 0)
	return lastindex;
    while (mainopts[index].name != NULL)
    {
	if (mainopts[index].val < OPT_AFFECT_OTHER && strcmp(mainopts[index].name, clave) == 0)
	    break;
	index ++;
    }
    if (mainopts[index].name == NULL)
    {
	printf("El comando indicado no existe, %s\n", clave);
	    fflush(stdout);
    }
    lastindex = index;
    return index;
}


